package com.psychologyCommunity.Community.Contoller;

import com.google.code.kaptcha.Producer;
import com.mysql.cj.util.StringUtils;
import com.psychologyCommunity.Community.entity.User;
import com.psychologyCommunity.Community.service.UserService;
import com.psychologyCommunity.Community.util.CommunityConstance;
import com.psychologyCommunity.Community.util.CommunityUtil;
import com.psychologyCommunity.Community.util.RedisKeyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Controller
public class LoginController implements CommunityConstance {
    @RequestMapping(value = "/register",method = RequestMethod.GET)
    public String getRegisterPage(){
        return "/site/register";
    }

    @Autowired
    private UserService userService;

    //需要有一个方法定义注册的请求 ，这个请求 是浏览器向我们 服务器提交数据 就是post
    @RequestMapping(path = "/register",method = RequestMethod.POST)
    public  String register(Model model, User user) throws IllegalAccessException {
        //只要页面传入的值 与user的属性相匹配，springMVC就会自动把这个值注入到user的属性
        Map<String, Object> map = userService.register(user);
        //如果注册成功 就到首页 ；激活之后就跳到登录页面
        if(map==null ||map.isEmpty()){
            //由register我们写的逻辑可以看到 要是map中为null ，就说明是注册成功了
            //此时我能就跳转到operate-result.html页面 ；并把值传进去
            model.addAttribute("msg","恭喜你成功注册心理健康网，给您发送了一份激活邮件，请尽快激活" +
                    "希望你能缓解压力,获得快乐");
            model.addAttribute("target","/index"); //之后可以在此页面跳到首页
            return "/site/operate-result";//跳转到操作结果的页面

        }else{
            //把页面上的消息都 回给model 显示给用户
            model.addAttribute("usernameMsg",map.get("usernameMessage"));
            model.addAttribute("passwordMsg",map.get("passwordMessage"));
            model.addAttribute("emailMsg",map.get("emailMessage"));
            return "/site/register";//如果注册失败再回到 注册页面重写注册
        }

    }
    //创建一个日志对象 ，以当前的类名来命名
    private static final Logger logger=LoggerFactory.getLogger(LoginController.class);

    @Autowired
    private Producer producerKaptcha;
    @Autowired
    private RedisTemplate redisTemplate;

    //要在登录页面 编写一个方法  给浏览器返回一个html，
    // 而这个html中 会包含一个图片的路径，浏览器会依据路径再次访问服务器 获得这个图片
    @RequestMapping(path = "/kaptcha",method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response/*, HttpSession session*/){
        //我们向浏览器输出的是一个图片，不是一个字符串，也不是一个网页--我们需要自己用response对象手动向浏览器输出
        //我们生成验证码后 服务端需要记住它，当你登陆时，你再次访问服务器时，我好验证，你这验证码对不对，这个验证码不能存在浏览器端，不然 很容易被盗取了
        //要存在服务器端，在多个请求之间要用，这次请求 生成 存到服务器，，登录的时候才用 --到时候获取session来用

        //生成验证码  --这个逻辑不变
        String text = producerKaptcha.createText();
        BufferedImage ImageAAAA = producerKaptcha.createImage(text);

        //将验证码存入session
//        session.setAttribute("kaptcha",text);
        //优化：验证码的归属
        String kaptchaOwner= CommunityUtil.generateUUID();
        Cookie cookie=new Cookie("kaptchaOwner",kaptchaOwner);
        cookie.setMaxAge(60);//设置 生存时间 ，60s 验证码一般很快就失效了
        cookie.setPath(contextPath);//有效路径，整个 路径下都 有效
        response.addCookie(cookie);//然后把它发给客户端
        //将验证码存入redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);//传入uuid生成redisKey
        redisTemplate.opsForValue().set(redisKey,text,60, TimeUnit.SECONDS);
     //---优化的部分


        //将图片输出给浏览器
        response.setContentType("image/png");//定义图片类型 就是png格式的 图片

        //将图片输出给浏览器
        try {
            //这个流 不需要关闭 ，因为这个response是由springMVC去维护的，他自动会关，我们拿来用就行了
            ServletOutputStream outputStreamAAAA = response.getOutputStream();
            ImageIO.write(ImageAAAA,"png",outputStreamAAAA);//输出的就是我们 刚刚生成的二维码的图片，以png的格式输出，并以outputStreamAAAA流的形式输出
        } catch (IOException e) {
           logger.error("响应验证码失败"+e.getMessage());
        }


    }

    //http://localhost:8888/community/activation/用户id/激活码
    //激活的时候 不是提交表单，而是利用路径携带两个参数 userid 和激活码 他最终希望获得一个结果（相当于查询） 所以还是get请求
    //从路径上拿到userId 和 激活码
    @RequestMapping(path = "/activation/{userId}/{code}",method = RequestMethod.GET)
    public String activation(Model model, @PathVariable("userId") int userId,@PathVariable("code") String code){
        int result=userService.activation(userId,code);//通过调用服务层的activation 方法可以得到一个 结果标志
        if(result==ACTIVATION_SUCCESS){//这个类的上面也要记得实现接口 才可以直接用接口中定义的常量
            //如果返回的是成功 就跳到登录页面
            model.addAttribute("msg","激活成功，您的账号已经可以正常使用");
            model.addAttribute("target","/login");
        }else if(result==ACTIVATION_FAILURE){//如果重复激活 就说明是无效操作  让他去首页
            model.addAttribute("msg","无效操作，该账号已经激活过了");
            model.addAttribute("target","/index");
        }else {//如果失败 就跳转到首页，让该用户去看看别的帖子
            model.addAttribute("msg","激活失败，您提供的激活码不正确");
            model.addAttribute("target","/index");
        }
        return "/site/operate-result";
    }

    //为了能以后在配置文件中灵活的 改变路径 可以在这声明一个成员变量 注入配置文件中的属性
    @Value("${server.servlet.context-path}")
    private String contextPath;

    //登录页面的路径跳转
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String getLoginPage(){
        return "/site/login";
    }

    //路径可以相同，此时需要请求方式不相同，但是如果路径相同，请求方式也相同，就会报错，因为并不能区分开
    @RequestMapping(path = "/login",method =RequestMethod.POST)
    // remember表示的是是否勾选记住我，返回响应时 也需要Mode ，页面传进来的验证码也要和我之前生成的图片上的验证码去比较（之前存在了session中 上面的方法）
    //所以也需要存session ;最后如果登录成功，我们把ticket发个客户端好让他保存 也需要创建一个cookie去存储，就需要一个response对象
    //扩展：如果形参是一个实体类对象，那么springMvc就会把这个对象装到Model中，可以在页面上直接得到这个实体类对象的数据 是从mode中拿的
    //如果是基本数据类型的参数，spring不会把参数放到model里，此时model中没有 ，方法一：就是人为的放到model中
// 方法二“   String username 、、、,boolean rememberMe 是请求中携带过来的，存在于request对象中，可以直接从request取值
    public String login(String username ,String password,String code,boolean rememberMe,
                        Model model,HttpSession session,HttpServletResponse response,
                        @CookieValue("kaptchaOwner") String kaptchaOwner){
        //先判断验证码对不对，验证码不对，用户信息都不用去看了；验证码直接在表现层判断出来，业务层只管业务逻辑，账号密码对不对
        String kaptcha= (String) session.getAttribute("kaptcha");
        //key不为空，表示 数据没有失效，才去取，否则就取不到，就直接回到登录页面给个 提示 验证码不正确
        if(!StringUtils.isNullOrEmpty(kaptchaOwner)){
            String redisKey =RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            kaptcha= (String) redisTemplate.opsForValue().get(redisKey);//得到的是Object，需要转型
        }

        //如果验证码是空的 或者你传入的code是空的也不对，比较session中的验证码 和你传的验证码 值不同  三种情况下 都不可以
        if (StringUtils.isNullOrEmpty(kaptcha) || StringUtils.isNullOrEmpty(code) || !kaptcha.equalsIgnoreCase(code)){
            model.addAttribute("CodeMsg","验证码不正确！");
            return "/site/login";
        }
        //检测账号密码 ，如果勾上 记住我 ，也是存在库里，不过定义的有效期很长
        // 此时去常量接口中定义两个常量比较方便 --默认的登录凭证的超时时间 和记住我状态下的登录凭证的超时时间
        int expiredSeconds=rememberMe ?REMEMBERME_EXPIRED_SECONDS:DEFAULT_EXPIRED_SECONDS;

        //如果验证码没问题 ，有效期也根据用户选择情况存完了 就可以登录进行用户验证了
        Map<String, Object> loginMap = userService.login(username, password, expiredSeconds);
        if(loginMap.containsKey("ticket")){ //如果用户密码也正确就会生成登录凭证
            Cookie cookie=new Cookie("ticket",loginMap.get("ticket").toString());
            cookie.setPath(contextPath);//设置整个cookie在整个项目都有效
            cookie.setMaxAge(expiredSeconds);//设置有效时间  --刚刚我们得到的时间
             response.addCookie(cookie);//然后最后在响应的时候把cookie发给浏览器

            return "redirect:/index";//登陆成功 直接让他去首页
        }else{//登陆失败 就返回登录页面重新登录 --这时候可能是有用户名 或者密码原因造成的 返回提示
            model.addAttribute("usernameMsgMo",loginMap.get("usernameMsg"));
            model.addAttribute("passwordMsgMo",loginMap.get("passwordMsg"));
            return "/site/login";
        }

    }
    //现在浏览器已经有cookie了 它会传给浏览器，怎么去拿了 通过注解(@CookieValue("ticket") 去拿传给参数
    @RequestMapping(path = "/logout",method = RequestMethod.GET)
    public String logout(@CookieValue("ticket") String ticket){
        userService.logOut(ticket);
        SecurityContextHolder.clearContext();
        return "redirect:/login";//如果登录不成功就 重新跳转到登录页面 重新登录
    }

}
